package lib;

import java.util.HashMap;
import java.util.ArrayList;
import java.util.Iterator;
import java.net.Socket;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.util.Set;
import java.util.Map;
import java.io.IOException;

public class SuperActor extends Actor{
	private HashMap<String, ArrayList<ActorAttributes>> actors;
	private String packageName;
	private boolean send;
	private ActorAttributes monitor;
	private String baseAddress;

	public SuperActor(ActorAddress myAddress, String packageName, ArrayList<String> actorsToCreate, String baseAddress) throws Exception {
		super(myAddress,myAddress,"SuperActor");
		actors=new HashMap<String, ArrayList<ActorAttributes>>();
		this.packageName=packageName;
		this.baseAddress = baseAddress;

		ActorAddress monitorAddress = new ActorAddress(baseAddress);

		monitor = new ActorAttributes(monitorAddress, (Actor) new Monitor(myAddress, monitorAddress), true);


		Iterator<String> it=actorsToCreate.iterator();
		String actorType;
		while(it.hasNext()){
			actorType=it.next();
			if (!createActor(actorType)) {
				throw new ClassNotFoundException(packageName+"."+actorType);
			}
		}
		
		startActor();
	}
	private boolean createActor(String actorType) {
		try {
			Class c=ClassLoader.getSystemClassLoader().loadClass(packageName + "." + actorType);
			if (c.getSuperclass().getName().equals("lib.Actor")){
				ActorAddress actorAddress=new ActorAddress(baseAddress);
				Constructor<Actor> constructor=c.getConstructor(Class.forName("lib.ActorAddress"), Class.forName("lib.ActorAddress"));
				Actor actor=(Actor)constructor.newInstance(myAddress,actorAddress);
				if(!actors.containsKey(actorType)) {
					actors.put(actorType, new ArrayList<ActorAttributes>());
				}
				boolean available=true;
				actors.get(actorType).add(new ActorAttributes(actorAddress,actor,available));
				System.out.println("New actor "+actorType+" created");
				return true;
			}else{
				return false;
			}
		}
		catch(ClassNotFoundException e){
			return false;
		}
		catch(Exception e){
			e.printStackTrace();
			return false;
		}
	}
	private void sendMsg(Message msg, ActorAddress dstActorAddress) throws Exception{
		System.out.println("SuperActor: Sending message to "+dstActorAddress);
		try {
			Socket conn=dstActorAddress.getSocket();
			ObjectOutputStream objOut=new ObjectOutputStream(conn.getOutputStream());
			objOut.writeObject(msg);
			conn.close();
		}
		catch (IOException e){
			String dstActorType = msg.getDestActorType();
			System.out.println(dstActorType+":"+dstActorAddress+" doesn't respond, he will be considered dead");
			removeActorAttrByAddress(dstActorType,dstActorAddress);
			if (!createActor(dstActorType)) {
				System.out.println("Error: Actor " + packageName + "." + dstActorType + " not found");
				return;
			}
			ActorAttributes actorAttr=actors.get(dstActorType).get(actors.get(dstActorType).size()-1);
			dstActorAddress=actorAttr.getAddress();
			sendMsg(msg,dstActorAddress);
		}
	}
	private ActorAttributes findActorAttrByAddress(String srcActorType,ActorAddress srcActorAddr){
		ActorAttributes actorAttr=null;
		Iterator<ActorAttributes> it=actors.get(srcActorType).iterator();
		boolean foundActor=false;
		while(it.hasNext()&&!foundActor){
			actorAttr=it.next();
			if (actorAttr.getAddress().equals(srcActorAddr)){
				foundActor=true;
			}
		}
		return actorAttr;
	}
	private void removeActorAttrByAddress(String srcActorType,ActorAddress srcActorAddr){
		ActorAttributes actorAttr=null;
		Iterator<ActorAttributes> it=actors.get(srcActorType).iterator();
		boolean foundActor=false;
		while(it.hasNext()&&!foundActor){
			actorAttr=it.next();
			if (actorAttr.getAddress().equals(srcActorAddr)){
				foundActor=true;
				it.remove();
			}
		}
	}
	private void cleanActorsByType(String actorType) throws Exception{
		ActorAttributes actorAttr;
		Iterator<ActorAttributes> it=actors.get(actorType).iterator();
		boolean foundAvailableActor=false;
		while(it.hasNext()&&!foundAvailableActor){
			actorAttr=it.next();
			if (actorAttr.available){
				foundAvailableActor=true;
			}
		}
		//Terminate all the other actors
		Message msg;
		while(it.hasNext()){
			actorAttr=it.next();
			msg=new Message(this.myAddress,"SuperActor",actorType,"Terminate",null);
			sendMsg(msg,actorAttr.getAddress());
			it.remove();
		}
	}
	protected boolean standardRoutine() throws Exception{
		this.standardRoutineIsUsed=false;
		return true;
	}
	protected boolean handleMessage(Message msg) throws Exception{
		String dstActorType=msg.getDestActorType();
		if (dstActorType.equals("SuperActor")) {
			if (msg.getOperation().equals("Terminate")){
				Set<Map.Entry<String, ArrayList<ActorAttributes>>> entries=actors.entrySet();
				Iterator it=entries.iterator();
				while(it.hasNext()){
					Map.Entry entry=(Map.Entry)it.next();
					Iterator itArrayList=((ArrayList<ActorAttributes>)entry.getValue()).iterator();
					while(itArrayList.hasNext()){
						ActorAttributes actorAttr=(ActorAttributes)itArrayList.next();
						msg=new Message(this.myAddress,"SuperActor",(String)entry.getKey(),"Terminate",null);
						sendMsg(msg,actorAttr.getAddress());
						System.out.println("Asking " + (String)entry.getKey() + " to terminate");
					}
				}

				System.out.println("Asking Monitor to terminate");
				msg = new Message(this.myAddress,"SuperActor","Monitor","Terminate",null);
				sendMsg(msg, monitor.getAddress());
				System.out.println("SuperActor will halt");
				return false;
			} else if (msg.getOperation().equals("Is dead")){
				ActorAddress srcActorAddr=msg.getSrcAddress();
				String srcActorType=msg.getSrcActorType();
				msg=new Message(this.myAddress,"SuperActor",srcActorType,"Terminate",null);
				sendMsg(msg,srcActorAddr);
				removeActorAttrByAddress(srcActorType,srcActorAddr);
				System.out.println(srcActorType+":"+srcActorAddr+" is dead");
			} else if (msg.getOperation().equals("Update my state")){
				String srcActorType=msg.getSrcActorType();
				if(!srcActorType.equals("SuperActor")){
					//The SuperActor doesn't update it's own state
					ActorAddress srcActorAddr=msg.getSrcAddress();
					ActorAttributes actorAttr=findActorAttrByAddress(srcActorType,srcActorAddr);
					if (actorAttr!=null){
						actorAttr.available = (!actorAttr.available);
					}
					if (actorAttr.available){
						cleanActorsByType(srcActorType);
					}
					System.out.println(srcActorType+":"+srcActorAddr+" has been updated");
				}
			} else if (msg.getOperation().equals("Kill me")){
				ActorAddress srcActorAddr=msg.getSrcAddress();
				String srcActorType=msg.getSrcActorType();
				msg=new Message(this.myAddress,"SuperActor",srcActorType,"Terminate",null);
				sendMsg(msg,srcActorAddr);
				removeActorAttrByAddress(srcActorType,srcActorAddr);
			} else if (msg.getOperation().equals("Route")) {
				if (!actors.containsKey(msg.getContent())) {
					actors.put((String)msg.getContent(), new ArrayList<ActorAttributes>());
				}
				actors.get(msg.getContent()).add(new ActorAttributes(msg.getSrcAddress(), null, true));

				System.out.println("Routing messages to " + msg.getSrcAddress() + " when " + msg.getContent());
			} else if (msg.getOperation().equals("Declare")) {

				String[] args = ((String)msg.getContent()).split(" to ");
				String[] address = args[1].split(":");
				ActorAddress remoteSuperActorAddress = new ActorAddress(address[0], new Integer(address[1]));
				System.out.println("Declaring actor " + args[0] + " to " + remoteSuperActorAddress);
				msg=new Message(myAddress,"SuperActor", "SuperActor","Route",args[0]);
				sendMsg(msg, remoteSuperActorAddress);
			}

			else {
				System.out.println("This received message is unrecognized :\n"+msg);
			}
		} else {

			ActorAttributes actorAttr = null;
			ActorAddress dstActorAddr = null;
			//Routing
			if(msg.getSuperActorOperation().equals("new")) {
				if (!createActor(dstActorType)) {
					System.out.println("Error: Actor " + packageName + "." + dstActorType + " not found");
					return true;
				}
				actorAttr=actors.get(dstActorType).get(actors.get(dstActorType).size()-1);
				dstActorAddr=actorAttr.getAddress();
				sendMsg(msg,dstActorAddr);
				//Terminate the newly created actor to not have numerous available actors of the same type
				msg=new Message(this.myAddress,"SuperActor",dstActorType,"Terminate",null);
				sendMsg(msg,dstActorAddr);
				removeActorAttrByAddress(dstActorType,dstActorAddr);
			}

			else {

				if (actors.containsKey(dstActorType)) {
					Iterator<ActorAttributes> it = actors.get(dstActorType).iterator();
					boolean foundActor = false;
					while (it.hasNext() && !foundActor) {
						actorAttr = it.next();
						if (actorAttr.available) {
							foundActor = true;
							dstActorAddr=actorAttr.getAddress();
						}
					}
					if (!foundActor){
						if (!createActor(dstActorType)) {
							System.out.println("Error: Actor " + packageName + "." + dstActorType + " not found");
							return true;
						}
						actorAttr = actors.get(dstActorType).get(actors.get(dstActorType).size() - 1);
						dstActorAddr = actorAttr.getAddress();
					}
				}

				else {
					if (!createActor(dstActorType)) {
						System.out.println("Error: Actor " + packageName + "." + dstActorType + " not found");
						return true;
					}
					actorAttr=actors.get(dstActorType).get(0);
					dstActorAddr=actorAttr.getAddress();
				}
				sendMsg(msg,dstActorAddr);
			}
		}
		return true;
	}
}
